package main

import (
	"fmt"
	"time"
)

func main() {
	/*
		time 包：
		1.简介：time包提供了时间显示和测量等所用的函数

		2.原理：
			（1）时间一般包含 时间值 和 时区
				type Time struct {
					wall uint64
					ext int64
					loc *Location
				}
				wall：表示距离 公元 1 年 1 月 1 日 00:00:00UTC 的描述
				 ext：表示 纳秒
				 loc：代表时区，主要处理偏移量，不同的时区，对应的时间不一样（时区变量）
			（2）标准时间：使用“原子震荡周期”所计算的物理时钟（Atomic Clock,也称为原子钟）
				UTC 就是利用Atomic Clock为基准所定义出来的正确时间。
					UTC标准时间是以 GMT（格林尼治时间）这个时区为主，所以本地时间与UTC时间的差值就是本地时间与GMT时间的差值

					UTC + 时区差 = 本地时间
					（eg：UTC + 8 小时 = 北京时间）
			（3）time包里，有两个时区变量：
				  time.UTC: UTC时间
				time.Local: 本地时间

			（4）获取 时区变量：
					  name 为 时区名称
					offset 为 与 UTC 的偏移量（单位：s）

					FixedZone(name string, offset int) *Location //返回一个距离UTC偏移固定时间的新的Location

					LoadLocation(name string) (*Location, error) //根据给定时区名称返回对应的Location
						函数会先从系统时区数据库中查找该名称对应的时区信息，
						如果找不到，则会从Go语言内置的时区数据库中查找，
						如果仍然找不到，则会返回一个错误信息

		3.时间的获取：
			（1）获取当前时间：
				time.Now()
						获取当前的 “时间对象”（Time），然后通过 时间对象 来获取当前的 时间信息。
					在时间对象上可以调用 Year()、Month()、Day()、Hour()、Minute()、Second() 等获取具体的年、月、日、小时、分钟、秒等

			（2）获取时间戳：
				Unix()、UnixNano()
					时间戳 是自 1970 年 1 月 1 日（08:00:00GMT）至当前时间的总毫秒数，它也被称为 Unix 时间戳
					    Unix()  时间戳
					UnixNano()  纳秒时间戳
				注：必须先通过 time.Now() 获取当前的时间对象，在时间对象上调用上述方法

				time.Unix(sec int64, nsec int64) 函数 可以 将时间戳转为时间格式（时间对象）
					sec：时间戳
					nsec：纳秒时间戳

					时间对象.Unix()/UnixNano()：时间对象 ——> 时间戳

					time.Unix(sec int64, nsec int64)：时间戳 ——> 时间对象


			（3）获取当前是星期几：
				时间对象.Weekday() //返回某个时间点所对应是一周中的周几

		4.时间操作函数
			（1）Add：
				将一个时间值加上一定的时间间隔，返回新的时间值。
				func (t Time) Add(d Duration) Time //在 时间对象Time 上定义的方法 Add，返回一个时间对象
					返回：时间点t + 时间间隔d 的值
					若要求 t-d，可以使用 t.Add(-d)

			（2）Sub
				求两个时间之间的差值
				func (t time) Sub(u Time) Duration //在 时间对象Time 上定义的方法 Sub，传入一个时间对象，返回两个时间对象间的时间间隔Duration
					返回：一个时间段 t - u 的值，如果结果超出Duration可以表示的最大值或最小值，将返回最大值或最小值

			（3）Equal
				判断两个函数是否相同
				func (t Time) Equal(u Time) bool
					Equal函数会考虑时区的影响，因此不同时区标准的时间也可以正确比较
					Equal方法 和 用 t==u 不同，Equal还会比较地点和时区信息

			（4）Before
				判断两个时间是否相同
				func (t Time) Before(u Time) bool
					如果 t 代表的时间点在 u 之前，则返回真，否则返回假

			（5）After
				判断一个时间点是否在另一个时间点之后
				func (t Time) After(u Time) bool
					如果 t 代表的时间点在 u 之后，则返回真，否则返回假

		5.定时器
			（1）NewTimer()
				time.NewTimer()
				实例化Timer结构体，在持续时间 d 之后发送当前时间至通道内
				用于创建一个新的 一次性 计时器，该计时器将至少在持续时间“d”之后在其通道上传输实际时间
				func NewTimer(d Duration) *Timer
					*Timer：是指向计时器的指针
					返回值：它返回一个通道，通知计时器必须等待多长时间
					type Timer struct {
						C <-chan Time
						r runtimeTimer //运行时计时器
					}

			（2）NewTicker()
				time.NewTicker()
				与 NewTime() 创建的 一次性 计时器不同，NewTicker() 创建的计时器是 周期性的，每隔一段时间就会触发一次

			（3）time.Tick()
				是对 time.NewTicker 的封装。最好不要使用这个方法，除非你准备将 chan 作为返回结果并在程序的整个生命周期中继续使用它。
					垃圾收集器无法恢复底层的 Ticker，出现：泄露“请谨慎使用，如果有疑问请改用 time.Ticker 类型
				使用 time.Tick(时间间隔) 可以设置定时器，定时器的本质上是一个通道，到时间就会将当前时间放入通道。
					time.Tick() 的功能和 NewTicker()几乎一样

				定时器 ticker 相当于一个通道，每隔一秒，就产生一个时间对象。（这个通道每隔一秒就放进一个当时时间的时间对象）

			（4）Reset()
				func (t *Timer) Reset(d Duration) bool
				使 t 重新开始计时，（本方法返回后再）等待时间段 d 过去后到期。
				如果调用时 t 还在等待中会返回 true，如果 t 已经到期或者被停止了会返回 false
				注：
				1.从你调用 Reset 这一刻开始，计时器就重新开始计时，并且在 Reset 指定的时间段 d 之后到期。
				2.当你 Reset 一个计时器时，计时器可能有两种情况：
					（1）如果计时器还在运行，Reset 令计时器重新开始计时并返回 true；
					（2）如果计时器已经到期，Reset 令计时器重新开始计时并返回 false。
				3.Reset的返回值不代表重启定时器成功或失败，而是在表达定时器在重设前的状态：
					（1）当Timer已经停止或者超时，返回false。
					（2）当定时器未超时时，返回true。

			Reset()妙用：
				前面介绍过，Timer 是一次性计时器，Ticker 是周期性计时器
				其实，我们可以用 Timer + Reset 实现周期性计时器

			（5）定时器总结：
				1.一次性定时：

				Timer：创建定时器，指定定时时长，定时到达后，系统会自动向定时器的成员 c 写入系统当前时间
					type Timer struct {
						C <-chan Time
						r runtimeTimer
					}
					常用操作：Sleep()、NewTimer、After
					读取 Timer.C 得到定时后的系统时间。并完成一次 chan 的读操作

				time.After() 定时：
					指定定时时长，定时到达后。系统会自动向定时器的成员写入系统当前时间
					返回可读 chan。读取，可获得系统写入时间。

				总结：Sleep、NewTimer、After —— time 包

				定时器的 停止、重置：
					（1）创建定时器 myTimer := time.NewTimer(2 * time.Second)
					（2）停止：myTimer.Stop —— 将定时器归零。		<-myTimer.C 会阻塞
					（3）重置：myTimer.Reset(time.Second)

				2.周期定时：
					type Ticker struct {
						C <-chan Time
						r runtimeTimer
					}
					（1）创建周期定时器 myTimer := time.NewTicker(time.Second)
						定时时长到达后，系统会自动向 Ticker 的 C 中写入系统当前时间。
						并且，每个一个定时时长后，循环写入系统当前时间。

					（2）在子 go程 中循环读取 C。获取系统写入的时间。

		6.时间格式化：
			Format()
			时间类型有一个自带的 Format 方法 进行格式化

			注：如果想将时间格式化为 12 个小时格式，需要指定 PM。
				通过在时间对象上使用 Format("字符串") 方法，可以将时间对象格式化为字符串指定的格式。

		7.解析字符串格式的时间
		（1）Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间
				func Parse(layout, value string) (Time, error)
					layout 的时间必须是"2006-01-02 15:04:05"这个时间，不管格式如何，时间点一定得是这个。

		（2）ParseInLocation 函数
				func ParseInLocation(layout, value string, loc *Location) (Time, error)

		ParseInLocation 函数和Parse 函数 的不同：
			（1）当缺少时区信息时，Parse 将时间解析为 UTC 时间，而 ParseInLocation 将返回值的 Location 设置为 loc;
			（2）当时间字符串提供了时区偏移量信息时，Parse会尝试匹配本地时区，而 ParseInLocation 会去匹配 loc。

	*/
	//2.（4）获取时区变量
	//创建一个纽约时区 Location，纽约比 UTC 慢 5 小时
	ny := time.FixedZone("EST", -5*60*60) //EST美国的一个时区
	fmt.Println(ny)                       //EST

	//加载纽约时区的Location
	ny1, err := time.LoadLocation("EST")
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	fmt.Println(ny1)

	//3.时间获取
	//(1)获取当时间
	now := time.Now() //返回 time.Time 对象
	fmt.Println(now)
	/*
		2023-06-05 21:36:11.0459326 +0800 CST m=+0.001778801
			年-月-日 时:分:秒.微秒 时区
				   +0800 —— 表示中国标准时间与 UTC 之间的时区偏移量，中国标准时间比 UTC 快 8 个小时
					 CST —— 代表中国标准时间
			+0.001778801 —— 表示该时间相对于某个基准时间的偏移量，时间戳偏移量
							这个基准时间可能是 程序开始的时间，也可能是 某个系统级别的时间标记点
	*/
	year := now.Year()
	month := now.Month()
	day := now.Day()
	hour := now.Hour()
	minute := now.Minute()
	second := now.Second()
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) //2023-06-05 21:38:29

	//(2)获取时间戳
	timestamp1 := now.Unix()     //时间戳
	timestamp2 := now.UnixNano() //纳秒时间戳

	fmt.Println("时间戳：", timestamp1)   //时间戳： 1685972884
	fmt.Println("纳秒时间戳：", timestamp2) //纳秒时间戳： 1685972884716856900

	timeObj1 := time.Unix(timestamp1, 0) //将时间戳转为时间对象
	timeObj2 := time.Unix(0, timestamp2)
	fmt.Println(timeObj1, "————", timeObj1.Year()) //2023-06-05 22:08:04 +0800 CST ———— 2023
	fmt.Println(timeObj2, "————", timeObj2.Year()) //2023-06-05 22:08:04.7372122 +0800 CST ———— 2023

	//(3)获取当前是星期几
	t := time.Now()                   //获取时间对象
	fmt.Println(t.Weekday().String()) //在时间对象上调用 Weekday() 方法     Monday
	fmt.Println(t.Weekday())          //Monday

	//4.时间操作函数
	//(1)Add
	t1 := time.Now()
	fmt.Println(t1) //2023-06-05 22:36:20.4691215 +0800 CST m=+0.015763901

	later := t1.Add(time.Hour) //当前时间加 1 小时后的时间
	fmt.Println(later)         //2023-06-05 23:36:20.4691215 +0800 CST m=+3600.015763901

	//(2)Sub
	t2 := time.Now()
	fmt.Println(t2) //2023-06-05 22:46:49.2272915 +0800 CST m=+0.006282801

	later2 := t2.Add(time.Hour) //当前时间加 1 小时后的时间
	fmt.Println(later)          //2023-06-05 23:46:49.2272915 +0800 CST m=+3600.006282801

	d := later2.Sub(t2)
	fmt.Println(d) //1h0m0s

	//(3)Equal
	tt1 := time.Date(2023, 06, 05, 14, 0, 0, 0, time.UTC)
	tt2 := time.Date(2023, 06, 05, 14, 0, 0, 0, time.UTC)

	if tt1.Equal(tt2) {
		fmt.Println("Equal: tt1 == tt2") // tt1 == tt2
	} else {
		fmt.Println("Equal: tt1 != tt2")
	}

	if tt1 == tt2 {
		fmt.Println("tt1 == tt2") // tt1 == tt2
	} else {
		fmt.Println("tt1 != tt2")
	}

	//(4)Before (5)After
	tt3 := time.Date(2023, time.June, 6, 11, 30, 0, 0, time.UTC)
	tt4 := time.Date(2023, time.June, 5, 10, 0, 0, 0, time.UTC)

	fmt.Println(tt3.Before(tt4)) //false
	fmt.Println(tt3.After(tt4))  //true

	//5.计时器
	//(1)NewTimer()
	//timer定时器，是到固定时间后，执行一次，然后结束
	newtimer := time.NewTimer(5 * time.Second)

	//读取 Timer.C 得到定时后的系统时间。并且完成一次 chan 的读操作。
	<-newtimer.C

	//程序运行之后至少经过 5 秒，才打印出 Timer is inactivated
	fmt.Println("Timer is inactivated")
	//因为我们设置了一个 5 秒的计时器，程序在阻塞了 5 秒之后才正常运行

	//Timer 使用完之后还可以通过调用 Reset 方法 再次启用 该通道
	newtimer.Reset(5 * time.Second)

	<-newtimer.C

	//Timer is inactivated 打印出来后 5 秒才打印 wu~wu~wu~!
	fmt.Println("wu~wu~wu~!")

	//(2)NewTicker
	//ticker
	t0 := time.NewTicker(time.Second * 2)
	defer t0.Stop()
	tag := 0
	for {
		tag++
		if tag == 10 {
			break
		}
		ttt := <-t0.C //每个两秒都会接受一次 NewTimer 发来的时间
		fmt.Println("Ticket running...", ttt.Second())
	}
	/*结果：			 秒数
	Ticket running... 24
	Ticket running... 26
	Ticket running... 28
	Ticket running... 30
	Ticket running... 32
	Ticket running... 34
	Ticket running... 36
	Ticket running... 38
	Ticket running... 40
	*/

	//(3)time.Tick()
	//定义一个 1 秒间隔的定时器，Tick 是周期定时器
	ticker := time.Tick(time.Second)
	tag0 := 0

	//遍历定时器ticker
	for i := range ticker {
		tag0++
		fmt.Println(i) //每秒都会执行任务
		if tag0 == 10 {
			break
		}
	}
	/*结果：
	2023-06-06 20:38:41.4769505 +0800 CST m=+29.035320901
	2023-06-06 20:38:42.467645 +0800 CST m=+30.026015401
	2023-06-06 20:38:43.4779021 +0800 CST m=+31.036272501
	2023-06-06 20:38:44.4642712 +0800 CST m=+32.022641601
	2023-06-06 20:38:45.4664875 +0800 CST m=+33.024857901
	2023-06-06 20:38:46.4756553 +0800 CST m=+34.034025701
	2023-06-06 20:38:47.4731695 +0800 CST m=+35.031539901
	2023-06-06 20:38:48.4677503 +0800 CST m=+36.026120701
	2023-06-06 20:38:49.4763152 +0800 CST m=+37.034685601
	2023-06-06 20:38:50.4762689 +0800 CST m=+38.034639301
	*/

	//(4)Reset()
	//Reset 返回 true
	start := time.Now()
	fmt.Println("startTime:-------------", start)
	timer := time.AfterFunc(2*time.Second, func() {
		fmt.Println("after func callback,elaspe:", time.Now())
	})

	time.Sleep(1 * time.Second)

	//time.sleep(3*time.Second)
	//Reset 在 Timer 还未触发时返回 true；触发了或 Stop 了，返回 false
	if timer.Reset(4 * time.Second) { //true
		fmt.Println("timer has not trigger!-------------", time.Now())
	} else {
		fmt.Println("timer had expired or stop!--------------", time.Now())
	}

	//保证上面的计时器时间到了能继续执行，不然会直接跳过这个函数，无法执行 timer.after
	time.Sleep(10 * time.Second)
	fmt.Println("end:-----------------", time.Now())

	//Reset 返回 false
	start1 := time.Now()
	fmt.Println("startTime------------", start1)
	timer1 := time.AfterFunc(2*time.Second, func() {
		fmt.Println("after func callback, elaspe:", time.Now())
	})

	//与上面的那段 true 代码的变化点：1 ——> 3
	time.Sleep(3 * time.Second)

	if timer1.Reset(4 * time.Second) { //false
		fmt.Println("timer has not trigger!--------", time.Now())
	} else {
		fmt.Println("timer had expired or stop!---------", time.Now())
	}

	time.Sleep(10 * time.Second)
	fmt.Println("end--------------", time.Now())

	//6.时间格式化 -Format()
	now1 := time.Now()
	fmt.Println(now1)

	//以下的代码实现将时间对象 now 按照指定的格式输出

	//格式化的模板为Go的出生时间 2006年1月2号15点04分 Mon Jan
	fmt.Println(now1.Format("2006年1月2号15点04分 Mon Jan"))
	//24小时制
	fmt.Println(now1.Format("2006-01-02 15:04:05.000 Mon Jan"))
	//12小时
	fmt.Println(now1.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
	fmt.Println(now1.Format("2006/01/02 15:04"))
	fmt.Println(now1.Format("15:04 2006/01/02"))
	fmt.Println(now1.Format("2006/01/02"))
	/*结果：
	2023年6月6号22点02分 Tue Jun
	2023-06-06 22:02:39.630 Tue Jun
	2023-06-06 10:02:39.630 PM Tue Jun
	2023/06/06 22:02
	22:02 2023/06/06
	2023/06/06
	*/

	//7.解析字符串格式的时间
	var layout string = "2006-01-02 15:04:05"
	var timeStr string = "2019-12-12 15:22:12"
	timeObj3, _ := time.Parse(layout, timeStr)
	fmt.Println(timeObj3) //2019-12-12 15:22:12 +0000 UTC
	timeObj4, _ := time.ParseInLocation(layout, timeStr, time.Local)
	fmt.Println(timeObj4) //2019-12-12 15:22:12 +0800 CST
}
